home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Inside Mac Games Volume 4 #4
/
IMG 36 April 1996.iso
/
Essentials
/
MGD 4⁄96ƒ
/
MGW1Codeƒ
/
MGWSound1.c
< prev
next >
Wrap
Text File
|
1996-04-01
|
26KB
|
597 lines
//==============================================================================================\\
// ------------------------------------------------------------------------------- \\
// MGWSound1.c version 1.0.0 copyright © 1993…1995 Jamie McCornack, john calhoun \\
// ------------------------------------------------------------------------------- \\
// *<-- FOR 68K CODE ONLY -->*
// Sound management utilities for Macintosh GameWriter 1.0.0, a training program… \\
// …for beginning Mac game programmers. MGW1 includes MGWExterns1.h, MGWUtilities1.c,… \\
// …MGWSound1.c, MGWGraphics1.c, MGWGraphicsBWLite1.c, HelloWorld.rsrc and an assortment… \\
// …of demo programs; projects HelloWorld1.π etc. and source code files HelloWorld1.c etc. \\
// A tutorial is available in Tricks of the Mac Game Programming Gurus, published… \\
// …by Hayden Books, August 1995. \\
// \\
// This code is offered by the copyright holders for no fee and for whatever use… \\
// …you care to make of it, but we do hope you remember where it came from. \\
// \\
// Please send bug reports to MacGameDev at America OnLine. macgamedev@aol.com \\
// Suggestions and observations are also appreciated. \\
// Updates and upgrades will be available now and then from the above e-mail address. \\
//==============================================================================================\\
#include "MGWExterns1.h"
#include <Sound.h>
// Here is the constant that the SoundUnit uses internally
#define kOurCallBack 911 // This arbitrary number will identify a call back as our own.
// Here is the "global" variable used by the sound routines.
Boolean gUserWantsSound; // A user-set variable used for turning on/off all sounds.
// Here are the internal variables used by MGWSound1.c. If you use the proper calls…
// …(see the routines in MGWExterns.h) you should never need to access these externally.
SndChannelPtr soundChannel; // This is the sound channel all our routines use
short soundPriority; // This is the priority of the sound currently playing
Boolean canUseSound; // Is FALSE if we can't use sound on this Mac
//------------------------------------------------------------ Prototypes
void FlushSoundNow (void);
void FlushSoundSoon (void);
pascal void SoundCallBack(SndChannelPtr chan, SndCommand theCommand);
OSErr InstallCallBack(void);
//============================================================== Functions
//-------------------------------------------------------------- CloseDownSound
// Call this function to dispose of the sound channel when switching out or quitting.
// Otherwise, sound may be disabled for the next program you run.
OSErr CloseDownSound(void)
{
OSErr theErr;
theErr = noErr; // Assume no error
if (soundChannel != nil) {
theErr = SndDisposeChannel(soundChannel, TRUE);
soundChannel = nil; // Set the variable to nil now
}
soundPriority = kNoSoundPlaying; // soundPriority needs to reflect no sound playing
return(theErr);
}
//-------------------------------------------------------------- InitializeForSound
// This procedure initializes all variables used by the sound unit routines.
// It also does a check using SysEnvirons() to determine if sound will run at all.
// You should have your program call this function only once at start up!
void InitializeForSound()
{
SysEnvRec thisWorld;
gUserWantsSound = TRUE;
soundChannel = nil; // Initialize our sound channel to nil (very important).
soundPriority = kNoSoundPlaying; // Indicate no sound is playing.
SysEnvirons(1, &thisWorld); // Call on SysEnvirons() to determine if we can play
// sound on this particular Mac and System version.
// Anything before the Mac II may not work for these routines.
// Anything before System 6.0.5 may not work for these routines.
canUseSound = ((thisWorld.machineType >= envMacII) && (thisWorld.systemVersion >= 0x0605));
}
//-------------------------------------------------------------- LoadASound
// Call this function to load a specific sound (by passing the ID of the 'snd ' resource
// in your program). It will load it, move it high in memory, and lock it. It will
// return FALSE if there was an error. Errors could be due to low memory or because
// the ID you specified was not found.
Boolean LoadASound(short soundID)
{
Handle theSoundHandle;
Boolean noProblem;
theSoundHandle = Get1Resource('snd ', soundID);
if (theSoundHandle == nil) { // If the memory manager didn't assign a handle to
noProblem = FALSE; // theSoundHandle, then there was an error.
} else {
noProblem = TRUE;
MoveHHi(theSoundHandle);
HLock(theSoundHandle);
}
return(noProblem); // Report back to calling routine.
}
//-------------------------------------------------------------- LoadARangeOfSounds
// This function loads a range of sounds (say from ID = 2000 to ID = 2014). You must
// be quite sure however that all those sounds exist. If anyone of them cannot be
// found (or if memory is too low to permit them all to load) the function will return
// FALSE indicating a failure to load one or more of the specified sounds. Note that
// this function just makes subsequent calls to the above function.
Boolean LoadARangeOfSounds(short firstID, short lastID)
{
short i;
Boolean noProblem;
noProblem = TRUE; // Start by assuming there won't be an error.
for (i = firstID; i <= lastID; i++) {
if (! LoadASound(i)) { // If LoadASound returned FALSE on any sound
noProblem = FALSE; // in the range, there was a problem.
}
}
return(noProblem); // Report back to calling routine.
}
//-------------------------------------------------------------- LoadAllSounds
// This function takes a different strategy for loading sounds. It uses the Resource
// Manager routines to indicate the number of 'snd ' resources in your program
// and then loads every one of them in the order that they appear in the resource fork.
Boolean LoadAllSounds()
{
Handle theSoundHandle;
short numberOfSounds, i;
Boolean noProblem;
noProblem = TRUE; // Start by assuming there won't be an error.
numberOfSounds = Count1Resources('snd '); // Determine # of sounds.
for (i = 1; i <= numberOfSounds; i++) { // Loop through all sounds.
theSoundHandle = Get1IndResource('snd ', i); // Get each sound.
if (theSoundHandle != nil) { // Did we get a valid sound?
MoveHHi(theSoundHandle); // Move sound handle high in memory
HLock(theSoundHandle); // And lock it!
} else {
noProblem = FALSE; // But if we didn't get a valid sound, then we failed.
}
}
return(noProblem); // Report back to calling routine.
}
//-------------------------------------------------------------- ReleaseASound
// Call this function to unlock a specific sound (by passing the ID of the 'snd ' resource
// in your program). Unlocking allows the memory manager to move or purge if needed.
// It will return FALSE if there was an error. Errors could be due to low memory (since it
// will load first if the resource is not in memory) or because the ID you specified was not found.
Boolean ReleaseASound(short soundID)
{
Handle theSoundHandle;
Boolean noProblem;
theSoundHandle = Get1Resource('snd ', soundID); // Finds sound handle in memory if it's loaded,
// otherwise loads it, so HUnlock will have a valid
// block to unlock.
if (theSoundHandle == nil) {
noProblem = FALSE;
} else {
noProblem = TRUE;
HUnlock(theSoundHandle); // Unlocks block, so Memory Manager can move or
} // purge it as needed.
return(noProblem); // Report back to calling routine.
}
//-------------------------------------------------------------- ReleaseARangeOfSounds
// This function unlocks a range of sounds (say from ID = 2000 to ID = 2014). You must
// be quite sure that all those sounds exist, though they need not be loaded into memory.
// If anyone of them cannot be found (or if memory is too low to permit one to load) the function
// will return FALSE indicating a failure to unlock one or more of the specified sounds.
// Note that this function just makes subsequent calls to the above function.
Boolean ReleaseARangeOfSounds(short firstID, short lastID)
{
short i;
Boolean noProblem;
noProblem = TRUE; // Start by assuming there won't be an error.
for (i = firstID; i <= lastID; i++) {
if (! ReleaseASound(i)) { // If ReleaseASound returned FALSE on any sound
noProblem = FALSE; // in the range, there was a problem.
}
}
return(noProblem); // Report back to calling routine.
}
//-------------------------------------------------------------- ReleaseAllSounds
// This function takes a different strategy for unlocking sounds. It uses a Resource
// Manager routine to indicate the number of 'snd ' resources in your program
// and then unlocks every one of them in the order that they appear in the resource fork.
Boolean ReleaseAllSounds()
{
Handle theSoundHandle;
short numberOfSounds, i;
Boolean noProblem;
noProblem = TRUE; // Start by assuming there won't be a problem.
numberOfSounds = Count1Resources('snd '); // Determine # of sounds.
for (i = 1; i <= numberOfSounds; i++) { // Loop through all sounds.
theSoundHandle = Get1IndResource('snd ', i);// Get each sound.
if (theSoundHandle != nil) { // Did we get a valid sound?
HUnlock(theSoundHandle); // And unlock it!
} else {
noProblem = FALSE; // But if we didn't get a valid sound, then we failed.
}
}
return(noProblem); // Report back to calling routine.
}
//-------------------------------------------------------------- SoundCallBack
// This procedure is never called from within the program. The Mac will call this
// procedure when the Sound Manager gets to a call back command in the sound queue.
// Because this procedure gets called at interrupt time. You cannot call any routine
// that moves memory or creates it! No GetResource(), NewHandle(), etc.
// Furthermore, this procedure must always be present in memory! For this to be
// guaranteed, you should make sure the routine is in the Main segment of your
// program. Put the whole SoundUnit in the Main memory segment in order to be safe.
// This procedure sets soundPriority to zero, so other routines
// can verify that a sound is no longer playing.
// Toolbox callback routines *must* be declared pascal
pascal void SoundCallBack(SndChannelPtr chan, SndCommand theCommand)
{
long theA5;
if (theCommand.param1 == kOurCallBack) { // Make sure it's our callBack.
theA5 = SetA5(theCommand.param2); // The A5 when InstallCallBack Routine was called.
soundPriority = kNoSoundPlaying; // Let the program know the sound is done.
theA5 = SetA5(theA5); // Restore the original A5. See IM6, 22-79.
}
}
//-------------------------------------------------------------- InstallCallBack
// This function also is only used internally by the SoundUnit. It is called right after
// a sound is played asynchronously. It will install the callBack command into the
// sound queue so that after a sound finishes playing, our call back routine is called
// and we can be alerted to the fact that a sound has finished playing. This function
// will return any error encountered.
OSErr InstallCallBack()
{
SndCommand theCommand;
theCommand.cmd = callBackCmd; // Set up the call back command.
theCommand.param1 = kOurCallBack; // Flag this call back command as our own.
theCommand.param2 = SetCurrentA5();
// Load the command into our sound channel.
return(SndDoCommand(soundChannel, &theCommand, FALSE));
}
//-------------------------------------------------------------- IsSoundOn
// This function can be called to determine if sound is both desired and possible.
Boolean IsSoundOn()
{
return (canUseSound && gUserWantsSound);
}
//-------------------------------------------------------------- ASoundIsPlaying
// A simple utility function that returns TRUE is a sound is playing or FALSE if not.
// This is how you can externally determine if a sound is playing.
Boolean ASoundIsPlaying()
{
return(soundPriority != kNoSoundPlaying);
}
//-------------------------------------------------------------- SoundPriorityPlaying
// A simple utility function that returns an integer indicating the priority of the
// current sound playing. It will return zero (kNoSoundPlaying) if that is the case.
// This is how you can externally check on the state of the variable 'soundPriority'.
short SoundPriorityPlaying()
{
return(soundPriority);
}
//-------------------------------------------------------------- ThisMacCanPlaySounds
// This utility allows the rest of your program access to the 'canUseSound' variable
// that the SoundUnit uses. This function will return TRUE if it is possible to play
// sound on this Mac or FALSE if it is not. As an example of this routines use, you
// may have a menu item called 'Sound On' that the user can check or uncheck in
// order to turn sounds in the game on or off. If however they are running on a Mac
// where it is impossible to play sounds, you would probably rather gray-out the menu
// item entirely. A call to the below function will let you know if sound is even an option.
Boolean ThisMacCanPlaySounds()
{
return(canUseSound);
}
//-------------------------------------------------------------- FlushSoundNow
// This routine is called internally to stop any sound currently playing and empty the…
// …command queu of all upcoming sounds and commands.
void FlushSoundNow (void)
{
SndCommand thisCommand;
OSErr thisErr;
thisCommand.cmd = flushCmd; // Flush any upcoming commands from the command queu.
thisCommand.param1 = 0; // This command ignores these parameters.
thisCommand.param2 = 0L;
thisErr = SndDoImmediate(soundChannel, &thisCommand);
thisCommand.cmd = quietCmd; // Send quietCmd to stop any current sound.
thisCommand.param1 = 0; // This command ignores these parameters.
thisCommand.param2 = 0L;
thisErr = SndDoImmediate(soundChannel, &thisCommand);
}
//-------------------------------------------------------------- FlushSoundSoon
// This routine is called internally to empty the command queu of all upcoming sounds…
// …and commands, but leave the current sound playing.
void FlushSoundSoon (void)
{
SndCommand thisCommand;
OSErr thisErr;
thisCommand.cmd = flushCmd; // Flush any upcoming commands from the command queu.
thisCommand.param1 = 0; // This command ignores these parameters.
thisCommand.param2 = 0L;
thisErr = SndDoImmediate(soundChannel, &thisCommand);
}
// =========================================================================================\\
// The Play…Sound routines \\
// =========================================================================================\\
// Here is where the results come from. At last, some routines that make sounds come out. \\
// =========================================================================================\\
//-------------------------------------------------------------- PlayASound
// Here we are. This is the work horse routine used by the game to play sounds
// asynchronously. After starting the sound playing, control is…
// immediately returned to our program. This allows the action to continue! You need
// simply specify the ID of the sound you want to play as well as the priority at which
// you want to play the sound (1 - 100). If a sound of higher priority is already playing,
// then the sound will not be played.
OSErr PlayASound(short soundID, short priority)
{
OSErr theErr;
Handle theSoundHandle;
if (! IsSoundOn()) return; // Exit if we are not to play sounds.
if (priority < soundPriority) return; // Exit if a higher priority sound is playing.
theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
if (! soundChannel == nil) // If soundChannel is open, flush out…
FlushSoundNow(); // …any commands in the soundChannel queu,…
// …and quiet the sound currently playing.
else
{ // Create our sound channel.
theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
if (theErr != noErr) return; // Exit if that failed.
}
// Play the sound asynchronously.
theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
if (theErr != noErr) return; // And exit if that failed.
soundPriority = priority; // Establish globally the priority of the sound playing.
InstallCallBack(); // Lastly, queue up a callBackCmd to notify us…
// when the sound has finished playing.
return(theErr);
}
//-------------------------------------------------------------- PlaySynchSound
// If you call this routine, the sound will be played and your program will wait here
// until the sound is finished (playing synchronously). Like the above routine, a sound
// priority must be passed in as well as the ID of the sound to be played synchronously.
// To override any other sound playing, pass it kHighestPriority for the priority.
OSErr PlaySynchSound(short soundID, short priority)
{
OSErr theErr;
Handle theSoundHandle;
if (! IsSoundOn()) return; // Exit if we are not to play sounds.
if (priority < soundPriority) return; // Exit if a higher priority sound is playing.
theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
if (! soundChannel == nil) // If soundChannel is open, flush out…
FlushSoundNow(); // …any commands in the soundChannel queu,…
// …and quiet the sound currently playing.
else
{ // Create our sound channel.
theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)SoundCallBack);
if (theErr != noErr) return; // Exit if that failed.
}
// Play the sound synchronously by passing FALSE.
theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, FALSE);
if (theErr != noErr) return; // Exit if that failed, but if it works,…
// …wait right here while the sound plays.
soundPriority = 0; // Establish globally that the sound is done playing.
return(theErr);
}
// =========================================================================================\\
// The Feeping Creature routines \\
// =========================================================================================\\
// "Creeping Featurism" is a threat to good programs. If you throw in everything but… \\
// …the kitchen sink, pretty soon you have an abomination like MS Word 6.0. \\
// Creeping featurism is particularly disgusting in games, many of which are so… \\
// …complex as to be unplayable. \\
// Use these routines with care. Sound should enhance, not overpower, your games. \\
// =========================================================================================\\
//-------------------------------------------------------------- PlayLoopSound
// A pretty useful sound routine. It darn near deserves to be above the Feeping Creatures line.
// Same as PlayASound except when PlayLoopSound encounters itself (or a sound with the same…
// …soundPriority number) already playing (or as the most recent entry in the sound channel queu).
// In that case, it calls FlushSoundSoon instead of FlushSound Now, and inserts the new sound…
// …into the queu. The sound currently playing continues to play, followed immediately by the…
// …soundID# passed to PlayLoopSound.
// Useful when making repeated calls for background sounds, to avoid "stuttering" due to the…
// …sound interrupting itself at inappropriate times or due to pausing between loops.
// If you're looping a sound that's six ticks long, and you pass the sound ID# to PlayLoopSound…
// and call it every five ticks, the sound will repeat itself seamlessly forever.
OSErr PlayLoopSound(short soundID, short priority)
{
OSErr theErr;
Handle theSoundHandle;
if (! IsSoundOn()) return; // Exit if we are not to play sounds.
if (priority < soundPriority) return; // Exit if a higher priority sound is playing.
//SysBeep(10); //TEST
theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
if (! soundChannel == nil) // If soundChannel is open, flush out any upcoming…
FlushSoundSoon(); // … commands in the soundChannel queu.
else
{ // Create our sound channel.
theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
if (theErr != noErr) return; // Exit if that failed.
}
// Play the sound asynchronously.
theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
if (theErr != noErr) return; // And exit if that failed.
soundPriority = priority; // Establish globally the priority of the sound playing.
InstallCallBack(); // Lastly, queue up a callBackCmd to notify us…
// when the sound has finished playing.
return(theErr);
}
//-------------------------------------------------------------- PlayLoopSoundOften
// This stuffs soundChannel with a loop that repeats over and over and over and over again.
// Use sparingly, or the player will get real real sick of it. Real real real real sick of it.
// Note the sound channel holds a finite number of commands (128 I think), and this routine…
// …will run synchronously (that is, play the sound over and over while everything else is…
// …locked up) for any sounds the queu can't hold. I don't pass more than 100 howManyLoops,…
// …but there's a commented-out check for that if you need it.
OSErr PlayLoopSoundOften(short soundID, short priority, short howManyLoops)
{
OSErr theErr;
Handle theSoundHandle;
int count;
if (! IsSoundOn()) return; // Exit if we are not to play sounds.
if (priority < soundPriority) return; // Exit if a higher priority sound is playing.
// if (howManyLoops > 100) // If an excessively large number is passed,…
// howManyLoops = 100; // …reduce it so it won't overflow the queu.
theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
if (! soundChannel == nil) // If soundChannel is open, flush out everything…
FlushSoundNow(); // …from the soundChannel command queu.
else
{ // Create our sound channel.
theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
if (theErr != noErr) return; // Exit if that failed.
}
for (count = 1; count <= howManyLoops; count++)
{ // Stuff the sound queu with sounds to play,…
theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
if (theErr != noErr) return; // …and exit if that failed.
}
soundPriority = priority; // Establish globally the priority of the sound playing.
InstallCallBack(); // Lastly, queue up a callBackCmd to notify us…
// …when the sound has finished playing.
return(theErr);
}
//-------------------------------------------------------------- AddSoundToQ
// AddSoundToQ does not flush the command queue from soundChannel, nor silence soundChannel, nor…
// …check the priority of the sound currently playing, nor establish its own priority.
// What it does is add (a command to play the selected sound) to the end of the soundChannel…
// …command queu, and set soundPriority to zero.
// Any call to any other Play…Sound routine will interrupt AddSoundToQ, and…
// …AddSoundToQ will add its sound to the queu created by any Play…Sound routine. This routine…
// …resides outside the entire soundPriority system, and must be called with care.
// So what's AddSoundToQ good for? Stringing a series of sounds together. Put individual words…
// in resource files, and play them back as sentences. Record assorted barnyard animal sounds,…
// …sort them by pitch, store them as 'snd ' resources, and play them back as music.
// The standard sound queu holds 128 commands; that's enough for some quite complex dialog…
//…from ground control, or for a flock of sheep to baa a full chorus of "In the Mood," but don't…
// …go over 128, or it'll play synchronously (that is, everything but the sound will lock up)…
// …until the queue works its way back down to 128 commands.
void AddSoundToQ (short soundID)
{
OSErr theErr;
Handle theSoundHandle;
if (! IsSoundOn()) return; // Exit if we are not to play sounds.
theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
if ( soundChannel == nil) // If there's no soundChannel, open it.
theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
if (theErr != noErr) return; // Exit if that failed.
// Add this sound to the the command queue.
theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
soundPriority = 0; // Set it here instead of using the sound callback…
// …routine--saves queue space.
}
//------------------------------------------------------------------------------------------\\
// End MGWSound1.c \\
//------------------------------------------------------------------------------------------\\